// View.cpp
#include <iostream>
#include <sstream>
#include "View.h"
#include "config.h"
#include <memory>
using namespace std;

float View::w_global = 0;
float View::h_global = 0;
/*
 * Constructeur de la vue qui initialise tous les attributs
 * nécessaires.
 */
View::View() {
	glShadeModel(GL_FLAT); // pour éviter des effets smooth non voulus

	/* pour des lignes antialiased */
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_BLEND);

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);

	glLineWidth(2.0); // pour épaissir un peu le texte
	model = Model::getInstance();

	quadDisque = gluNewQuadric();
	gluQuadricDrawStyle(quadDisque, GLU_FILL); // remplissage des cercles
	//Background
	glClearColor(0.23, 0.23, 0.23, 0); // Définit la couleur à utiliser lors des appels à glClear(...)
	smoothlag = 0.0;
}

View::~View() { // terminer/remplir
}

/*
 * initialise et créé les display lists
 */
void View::initDisplayList() {
	/*création des display lists */
	//initialisation des variables des viewports du bas et haut
	dlFond = glGenLists(19);
	dlViesPacman = dlFond + 1;
	dlResetButton = dlFond + 2;
	dlQuitButton = dlFond + 3;
	dlUpArrow = dlFond + 4;
	dlDownArrow = dlFond + 5;
	dlLeftArrow = dlFond + 6;
	dlRightArrow = dlFond + 7;

	//initialisation des variables de la viewport centrale
	dlBiere = dlFond + 8;
	dlJackie = dlFond + 9;
	dlGhost = dlFond + 10;
	dlGhost2 = dlFond + 11;
	dlMurInt = dlFond + 12;
	dlMurExtH = dlFond + 13;
	dlMurExtV = dlFond + 14;
	dlCoinExt = dlFond + 15;
	dlMurPrison = dlFond + 16;
	dlGhost3 = dlFond + 17;
	dlMiddlePoint = dlFond + 18;
	//display list fond
	glNewList(dlFond, GL_COMPILE);
	dessineFond();
	glEndList();

	//display list vies pacman
	glNewList(dlViesPacman, GL_COMPILE);
	gluPartialDisk(quadDisque, 0.0, CELL_SIZE / 2.0, 100.0, 1.0, ANGLEDEPARTOUVERT,
			ANGLEENTIEROUVERT);
	glEndList();

	//display list bouton reset
	glNewList(dlResetButton, GL_COMPILE);
	boutonReset();
	glEndList();

	//display list bouton quit
	glNewList(dlQuitButton, GL_COMPILE);
	boutonQuit();
	glEndList();

	//display list flèche haut
	glNewList(dlUpArrow, GL_COMPILE);
	glBegin(GL_TRIANGLES);
	glVertex2f(0.0, 0.0);
	glVertex2f(-3, -2.0);
	glVertex2f(3.0, -2.0);
	glEnd();
	glEndList();

	//display list flèche bas
	glNewList(dlDownArrow, GL_COMPILE);
	glBegin(GL_TRIANGLES);
	glVertex2f(-3.0, -6.0);
	glVertex2f(0.0, -8.0);
	glVertex2f(3.0, -6.0);
	glEnd();
	glEndList();

	//display list flèche gauche
	glNewList(dlLeftArrow, GL_COMPILE);
	glBegin(GL_TRIANGLES);
	glVertex2f(-3, -2.0);
	glVertex2f(-5, -4.0);
	glVertex2f(-3.0, -6.0);
	glEnd();
	glEndList();

	//display list flèche droite
	glNewList(dlRightArrow, GL_COMPILE);
	glBegin(GL_TRIANGLES);
	glVertex2f(3.0, -6.0);
	glVertex2f(5.0, -4.0);
	glVertex2f(3.0, -2.0);
	glEnd();
	glEndList();

	/*
	 * display list de la viewport du milieu
	 */

	//display list biere(pastille)
	glNewList(dlBiere, GL_COMPILE);
	dessineBiere();
	glEndList();

	//display list jackie(super pastille)
	glNewList(dlJackie, GL_COMPILE);
	dessineJackie();
	glEndList();

	//display list ghost(voiture) rouge
	glNewList(dlGhost, GL_COMPILE);
	dessineGhost(1.0, 0.0, 0.0);
	glEndList();

	//display list ghost(voiture) bleu
	glNewList(dlGhost2, GL_COMPILE);
	dessineGhost(0.0, 0.0, 1.0);
	glEndList();

	//display list ghost(voiture) verte
	glNewList(dlGhost3, GL_COMPILE);
	dessineGhost(0.0, 1.0, 0.0);
	glEndList();

	//display list mur interieur
	glNewList(dlMurInt, GL_COMPILE);
	dessineMurInterieur();
	glEndList();

	//display list mur extérieur horizontal
	glNewList(dlMurExtH, GL_COMPILE);
	dessineMurExHor();
	glEndList();

	//display list mur extérieur vertical
	glNewList(dlMurExtV, GL_COMPILE);
	dessineMurExVert();
	glEndList();

	//display list coin extérieur
	glNewList(dlCoinExt, GL_COMPILE);
	dessineCoinExt();
	glEndList();

	//display list mur prison
	glNewList(dlMurPrison, GL_COMPILE);
	dessineMurPrison();
	glEndList();

	//display list middle point
	glNewList(dlMiddlePoint, GL_COMPILE);
	dessineMiddlePoint();
	glEndList();
}

/*
 *  Fonction permettant le rafraichissement de l'affichage -> actionné par le controlleur
 */
void View::redraw() {
	/*pour réaliser un mouvement continu-> demande le display 'SMOOTHMOVESLAG' fois
	 * en passant en paramètre à la méthode display le ratio auquel le décalage doit être appliqué.
	 * Pour SmoorthLag de 10 -> 0.9, 0.8, 0.7, ... jusque 0.3
	 */
	for (float i = (SMOOTHMOVESLAG - 1); i >= 0.0; i--) { //9
		smoothlag = (CELL_SIZE / SMOOTHMOVESLAG) * i;
		display();
	}
}

/*
 * Définition de la viewport top et ses caractéristiques de projection.
 * Elle est utilisée afin d'afficher le tableau de jeu (viewport du milieu)
 */
void View::viewportTop() {
	/*il se pourrait que ces propriétés aient été activées par les viewport 3D,
	 * Il faut les désactivées car cet viewPort n'affiche que du 2D
	 */
	glDisable(GL_DEPTH_TEST); //desactive le test de profondeur
	glDisable(GL_LIGHTING); //desactiver l'éclairage
	glDisable(GL_LIGHT0); //desactiver la source de lumière 0
	glDisable(GL_COLOR_MATERIAL); //desafficher les couleurs lorsque l'éclairage est activé

	// la viewport2 prend 1/5 de la fênetre
	glViewport(0, (h_global * 4) / 5, w_global, h_global / 5); // Taille et positionnement de la viewport
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity(); // Initialisation de la matrice de projection
	/*gérer distortion*/
	distortionTop(w_global, h_global / 5);
	/*fin gérer distortion*/

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity(); // Initialisation de la matrice de modelview
}

/*
 * Définit la viewport bottom et ses caractéristiques de projection
 * Elle est utilisée afin d'afficher les flèches directionnelles, les boutons quitter/rejouer(viewport du bas)
 */
/*
 * Méthode permettant de dessiner ce qui sera afficher à l'écran.
 */
void View::display() {
	PacMan pacman = model->getPacMan();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Initialisation du buffer de couleur pour la couleur de fond

	/* haut de l'écran*/
	glPushMatrix();

	viewportTop();

	glCallList(dlFond); // pour donner une couleur de fond à la viewport

	glPopMatrix();

	glPushMatrix();

	glColor3f(0.0, 0.0, 0.0);
	//concate string avec int
	string texte = "Score: ";
	std::stringstream sstm;
	sstm << texte << model->getScore() / 100.0 << " grammes";
	string result = sstm.str();

	glTranslatef(-10.0, -2.5, 0);
	glScalef(0.01, 0.01, 0.01);
	ecrire(result); // ecrit le score dans la viewport du haut
	glPopMatrix();

	glPushMatrix();
	std::stringstream sstm2;
	texte = "Vies: ";

	sstm2 << texte;
	result = sstm2.str();
	glTranslatef(5.0, -2.5, 0);
	glScalef(0.01, 0.01, 0.01);
	ecrire(result);
	glPopMatrix();
	/*
	 * dessine X pacman en fonction du nombre de vies restantes
	 */
	for (int i = 0; i < pacman.getVieRestantes(); i++) {
		glPushMatrix();
		glTranslatef(8.2 + i, -2.1, 0);
		if (pacman.isModeSuper()) {
			glColor3f(1.0, 0.0, 0.0);
		} else {
			glColor3f(1.0, 0.8, 0.0);
		}
		glCallList(dlViesPacman);
		glPopMatrix();
	}

	glPushMatrix();
	// message de fin de niveau
	texte = model->getMsgToShow();

	glTranslatef(-1.0, -4.5, 0);
	glScalef(0.01, 0.01, 0.01);
	glColor3f(0.0, 0.0, 0.0);
	ecrire(texte);

	glPopMatrix();

	/* bas de l'écran*/
	glPushMatrix();
	viewportBottom();
	glCallList(dlFond);
	flechesDirection();

	glPushMatrix();
	glCallList(dlResetButton);
	glPopMatrix();

	glPushMatrix();
	glCallList(dlQuitButton);
	glPopMatrix();

	glPopMatrix();

	/* milieu écran */
	glPushMatrix();

	viewPortMiddle();
	dessineJeu(); // dessine tout ce qui est lié au jeu
	if (model->isGameEnded()) { //dessine un texte pour l'utilisateur au dessus du plateau de jeu si le jeu est fini
		popUp(model->getEndMsg());
	}
	glPopMatrix();
	glutSwapBuffers(); // Car on travaille en mode "double buffer", cf. main.cpp
}

/*  */
float View::getSmoothlag() const {
	return smoothlag;
}

/* */
void View::setSmoothlag(float smoothlag) {
	this->smoothlag = smoothlag;
}

/*
 * Méthode dessinant tout ce qui est lié à la platforme de jeu (viewport 1 du mileu).
 * Pour représenter des éléments de jeu nous utilisons des caractères dont voici la légende:
 * 		M: Pacman
 * 		P: Pastille
 * 		S: Super Pastille
 * 		G: Ghost
 * 		W: Mur interne
 * 		H: Mur externe horizontal
 * 		V: Mur externe vertical
 * 		J: Mur de 'prison' pour les ghost
 * 		B: Case de renaissance pour les ghosts ayant été mangés
 * 		E: Case vide
 * 		C: Coin Mur Externe
 * 		L: limite(Mur invisible) du terrain par lequel le pacman peut passé pour changer de coté
 */
void View::dessineJeu() {
	PacMan pacman = model->getPacMan();
	/*
	 * Traitement général de l'affichage des éléments du jeu sauf les ghosts
	 */

	for (int i = 0; i < model->getLabyrinthe()->getMatrixHeight(); i++) {
		for (int j = 0; j < model->getLabyrinthe()->getMatrixWidth(); j++) { //parcours chaque élement de la matrice
			glPushMatrix();

			float decaleX = j
					- (model->getLabyrinthe()->getMatrixWidth() / 2.0); //décalage en X entre la matrice logique et une matrice cartésienne(glut)
			float decaleY = -i
					+ (model->getLabyrinthe()->getMatrixHeight() / 2.0); //décalage en Y entre la matrice logique et une matrice cartésienne(glut)
			char cellType = model->getLabyrinthe()->getGamePanel()[i][j];
			glTranslated(decaleX, decaleY, 0); //décale pour le centré, en fonction de la hauteur et de la largeur divisée par 2(sinon deplacera coin sup gauche)
			if (cellType == 'P') { //pastille
				glCallList(dlBiere);
			} else if (cellType == 'M') { //pacman
				if (!pacman.isIdle()) { // si le pacman est en mouvement
					applySmoothMoves(pacman, smoothlag); // on applique le mouvement continu au pacman
					applySmoothPacmanAnimation(smoothlag); //applique un mouvement continu de la bouche du pacman
				}
				dessinePacman();
			} else if (cellType == 'W') { //mur intérieur
				glCallList(dlMurInt);
			} else if (cellType == 'H') { // mur extérieur horizontal
				glCallList(dlMurExtH);
			} else if (cellType == 'V') { // mur extérieur vertical
				glCallList(dlMurExtV);
			} else if (cellType == 'C') { // coin mur extérieur
				glCallList(dlCoinExt);
			} else if (cellType == 'S') { //super pastille
				glCallList(dlJackie);
			} else if (cellType == 'J') { //jail- mur de 'prison'
				glCallList(dlMurPrison);
			}
			glPopMatrix();
		}
	}

	/*
	 * Traitement spécial reservé aux ghosts étant donné que chaque ghost à des propriétés différentes pouvant
	 * influencer sa position dans la view
	 */

	for (int i = 0; i < model->getNombreGhost(); i++) { //parcours tous les ghosts
		glPushMatrix();
		float decaleX = model->getGhost(i).getCurrentPosX()
				- (model->getLabyrinthe()->getMatrixWidth() / 2.0); //décalage en X entre la matrice logique et une matrice cartésienne(glut)
		float decaleY = -model->getGhost(i).getCurrentPosY()
				+ (model->getLabyrinthe()->getMatrixHeight() / 2.0); //décalage en Y entre la matrice logique et une matrice cartésienne(glut)
		glTranslated(decaleX, decaleY, 0); // application du décalage
		applySmoothMoves(model->getGhost(i), smoothlag); //applique toujours le mouvement continu car les ghosts ne s'arrêtent jamais
		if (model->getGhost(i).getCurrentDirection() == UPDIRECTION
				|| model->getGhost(i).getCurrentDirection() == DOWNDIRECTION) { //si le ghost va vers le haut ou le bas, il faut lui faire faire une rotation
			glRotatef(-90, 0.0, 0.0, 1.0);
		}

		if (pacman.isModeSuper())
			glCallList(dlGhost3);
		else if (model->isLightRed())
			glCallList(dlGhost);
		else
			glCallList(dlGhost2);

		glPopMatrix();
	}

	/*
	 * Dessine le rond centrale permettant de faire tourner le plan sur lui-même
	 */
	glCallList(dlMiddlePoint);
}

/* Récupère les dimensions de la fentre d'écran */
void View::reshape(float w, float h) {
	w_global = w;
	h_global = h;
}

/*
 * Distortion appliquer à la viewportTop
 */
void View::distortionTop(float w, float h) {
	d1 = -15;
	delta1 = 30;

	d2 = -5;
	delta2 = 5;

	/*gérer distortion*/
	if ((w / h) > (delta1 / delta2))
		gluOrtho2D(d1 - (1. / 2.) * (((w * delta2) / h) - delta1),
				d1 + delta1 + (1. / 2.) * (((w * delta2) / h) - delta1), d2,
				d2 + delta2);

	if ((w / h) <= (delta1 / delta2))
		gluOrtho2D(d1, d1 + delta1,
				d2 - (1. / 2.) * (((h * delta1) / w) - delta2),
				d2 + delta2 + (1. / 2.) * (((h * delta1) / w) - delta2));
	/*fin gérer distortion*/
}

/*
 * Distortion appliquer à la viewportBottom
 */
void View::distortionBottom(float w, float h) {
	d1 = -25;
	delta1 = 50;
	d2 = -10;
	delta2 = 9;
	/*gérer distortion*/
	if ((w / h) > (delta1 / delta2))
		gluOrtho2D(d1 - (1. / 2.) * (((w * delta2) / h) - delta1),
				d1 + delta1 + (1. / 2.) * (((w * delta2) / h) - delta1), d2,
				d2 + delta2);

	if ((w / h) <= (delta1 / delta2))
		gluOrtho2D(d1, d1 + delta1,
				d2 - (1. / 2.) * (((h * delta1) / w) - delta2),
				d2 + delta2 + (1. / 2.) * (((h * delta1) / w) - delta2));
	/*fin gérer distortion*/
}

/*
 * Méthode permettant de dessiner les quatre flèches de direction
 */
void View::flechesDirection() {

	glPushMatrix();

	glTranslatef(0.0, -2.0, 0.0);
	if (model->getPushUp()) {
		glColor3f(1.0, 1.0, 1.0);
	} else
		glColor3f(0.1, 0.1, 0.1);
	//flèche up
	glCallList(dlUpArrow);

	if (model->getPushLeft()) {
		glColor3f(1.0, 1.0, 1.0);
	} else
		glColor3f(0.1, 0.1, 0.1);
	//flèche left
	glCallList(dlLeftArrow);

	if (model->getPushDown()) {
		glColor3f(1.0, 1.0, 1.0);
	} else
		glColor3f(0.1, 0.1, 0.1);
	//flèche down
	glCallList(dlDownArrow);

	if (model->getPushRight()) {
		glColor3f(1.0, 1.0, 1.0);
	} else
		glColor3f(0.1, 0.1, 0.1);
	//flèche right
	glCallList(dlRightArrow);

	glPopMatrix();
}

/*
 * Méthode permettant de dessiner le bouton "Reset"
 */
void View::boutonReset() {
	glPushMatrix();
	glColor3f(0.1, 0.1, 0.1);
	glRectf(-20.0, -10.0, -10.0, -2.0);

	glColor3f(0.89, 0.89, 0.89);
	glScalef(0.02, 0.02, 0.02);
	glTranslatef(-900.0, -350.0, 0);
	string reset = "Reset";
	ecrire(reset);

	glPopMatrix();
}

/*
 * Méthode permettant de dessiner le bouton "Quit"
 */
void View::boutonQuit() {
	glPushMatrix();
	glColor3f(0.1, 0.1, 0.1);
	glRectf(10.0, -2.0, 20.0, -10.0);
	glColor3f(0.89, 0.89, 0.89);

	glScalef(0.02, 0.02, 0.02);
	glTranslatef(650.0, -350.0, 0);
	string quit = "Quit";
	ecrire(quit);

	glPopMatrix();
}

/*
 * Dessine un rectangle dans le deux petites viewport pour donner une autre couleur de fond.
 */
void View::dessineFond() {
	glColor3f(0.92, 0.92, 0.92);
	glRecti(-1000, -1000, 1000, 1000);
}

/*
 * Popup qui affiche un texte indiquant si on a gagné ou perdu une partie
 */
void View::viewportBottom() {
	/*il se pourrait que ces propriétés aient été activées par les viewport 3D,
	 * Il faut les désactivées car cet viewPort n'affiche que du 2D
	 */
	glDisable(GL_DEPTH_TEST); //desactive le test de profondeur
	glDisable(GL_LIGHTING); //desactiver l'éclairage
	glDisable(GL_LIGHT0); //desactiver la source de lumière 0
	glDisable(GL_COLOR_MATERIAL); //desafficher les couleurs lorsque l'éclairage est activé

	// la viewportBottom prend 1.5/5 de la fênetre
	glViewport(0, 0, w_global, (h_global * 1.5) / 5); // Taille et positionnement de la viewport
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity(); // Initialisation de la matrice de projection
	/*gérer distortion*/
	distortionBottom(w_global, (h_global * 1.5) / 5);
	/*fin gérer distortion*/

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity(); // Initialisation de la matrice de modelview
}

void View::popUp(string m) {

	glPushMatrix();

	glTranslatef(-model->getLabyrinthe()->getMatrixWidth() / 1.9,
			-model->getLabyrinthe()->getMatrixHeight() / 2.4, 1.0);
	glColor3f(0.1, 0.1, 0.1);
	glRectf(0.0, 0.0, model->getLabyrinthe()->getMatrixWidth(),
			model->getLabyrinthe()->getMatrixHeight());

	glPopMatrix();

	glPushMatrix();

	glLineWidth(15.0); // pour épaissir un peu le texte
	glColor3f(1.0, 0.0, 0.0);

	glTranslatef(-model->getLabyrinthe()->getMatrixWidth() / 2.15,
			-model->getLabyrinthe()->getMatrixHeight() / 4, 1.0);
	float ratio = model->getLabyrinthe()->getMatrixWidth() / 82.0;
	ratio /= 10.0;

	glScalef(ratio, ratio, 0.5);
	ecrire(m);
	glLineWidth(1.5); // pour épaissir un peu le texte

	glPopMatrix();
}

/*
 * Méthode permettant d'appliquer un décalage(translation) sur un mouvable(pacman ou ghost)
 * de sorte qu'au final avec plusieurs décalages, le mouvable donne l'impression
 * d'avoir une mouvement continu
 */
void View::applySmoothMoves(Mouvable mouvable, float smoothLag) {
	if (mouvable.getCurrentDirection() == LEFTDIRECTION) {
		glTranslatef(smoothLag, 0, 0);
	} else if (mouvable.getCurrentDirection() == RIGHTDIRECTION) {
		glTranslatef(-smoothLag, 0, 0);
	} else if (mouvable.getCurrentDirection() == UPDIRECTION) {
		glTranslatef(0, -smoothLag, 0);
	} else if (mouvable.getCurrentDirection() == DOWNDIRECTION) {
		glTranslatef(0, smoothLag, 0);
	}
}


/*
 * Méthode permettant d'écrire un text donné en paramètre
 */
void View::ecrire(string text) {

	glPushMatrix();

	char c;

	for (unsigned offset = 0; offset < text.length(); offset++) {

		c = text.at(offset);

		glutStrokeCharacter(GLUT_STROKE_ROMAN, c);

	}

	glPopMatrix();

}
